/*
@20240621
针对适配云大单芯片相机的驱动读出板的socket服务端程序，修改了与CSST巡天模块CCD测试Labview程序进行交互。

与Labview交互的通信协议
序号      客户端         服务端             备注
1       readout     readoutdone    
2       refresh     refreshdone
3       expstart    /               服务端打印曝光开始提示
4       expdone     /               服务端打印曝光结束提示
5       9位数字串      tagreceived
6       exit        /               服务端程序退出
*/

#include "SchmidtCAM.h"
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/shm.h>
 
#define PORT            1001
#define LISTEN_QUEUE    10
#define BUFFER_SIZE     1024
#define RD_REG_NUM      12


#if defined(_ENABLE_FAST_READ_)
	#define _FAST_OP_ 1
#else
	#define _FAST_OP_ 0
#endif

/////////////////////////////////////////////////////////////////////////
// global parameters
/*time_t t_exp_start, t_exp_done;*/

int USE_PINNING = 0;
int power_status= 0;

struct timeval tv_pin_start;
struct timeval tv_pin_end;

struct timeval tv_exp_start;
struct timeval tv_exp_end;

struct timeval tv_read_start;
struct timeval tv_read_end;

// 备份CCD驱动时序参数
// backup vtrans parameters
int vtrans_num_bak; //      = mcfg->config_ccd_drive->vtrans_num;
int vtrans_skip_num_bak; //      = mcfg->config_ccd_drive->vtrans_num;
int imgCLK1_cnt_min_bak;    // = mcfg->config_ccd_drive->imgCLK1_cnt_min;
int imgCLK1_cnt_max_bak;    // = mcfg->config_ccd_drive->imgCLK1_cnt_max;
int imgCLK2_cnt_min_bak;    // = mcfg->config_ccd_drive->imgCLK2_cnt_min;
int imgCLK2_cnt_max_bak;    // = mcfg->config_ccd_drive->imgCLK2_cnt_max;
int imgCLK3_cnt_min_bak;    // = mcfg->config_ccd_drive->imgCLK3_cnt_min;
int imgCLK3_cnt_max_bak;    // = mcfg->config_ccd_drive->imgCLK3_cnt_max;
int imgCLK4_cnt_min_bak;    // = mcfg->config_ccd_drive->imgCLK4_cnt_min;
int imgCLK4_cnt_max_bak;    // = mcfg->config_ccd_drive->imgCLK4_cnt_max;
int imgCLKx_cnt_MAX_bak;    // = mcfg->config_ccd_drive->imgCLKx_cnt_MAX;
int vtrans_state_cnt_max_bak;   // = mcfg->config_ccd_drive->vtrans_state_cnt_max;

// backup htrans parameters
int htrans_num_bak; // = mcfg->config_ccd_drive->htrans_num;

SchmidtCAM_Config *mcfg;
/////////////////////////////////////////////////////////////////////////

int main( int argc, char *argv[] ){

    // 检查refresh时是否采用pinning模式
    if( argc == 2 ){
        USE_PINNING = atoi(argv[1]);
        if( USE_PINNING != 0 && USE_PINNING != 1 ){
            printf("## ERROR: USE_PINNING must be 0 or 1 !!!!\n");
            return 0;
        }
        
        if( USE_PINNING == 1 ){
        	printf("\n#############################################\n");
            printf("# refresh will be executed with pinning mode.\n");
            printf("#############################################\n");
        }
        else{
        	printf("\n################################################\n");
            printf("# refresh will be executed without pinning mode.\n");
            printf("################################################\n");
        }
    }

    dictionary *scan_param_ini;
    scan_param_ini = iniparser_load("scan_params.ini");
    if( scan_param_ini == NULL ){
        fprintf(stderr, "|##> cannot parse file: scan_params.ini\n");
        return -1;
    }
    
    // 检查电路板是否已经上电
    FILE *fp_tmp = fopen(".power_status", "r");
    fscanf(fp_tmp, "%d", &power_status);
    fclose(fp_tmp);
    
    if( power_status != 1 ){
        printf("# drive & readout PCB has not been properly powered up!\n");
        return 0;
    }
    
    //=================================================
    //  导入所有配置参数
    //=================================================
    char config_dir[1024];      // 保存参数配置文件所在的目录(相对路径)
/*    sprintf(config_dir, "%s", argv[1]);*/

    FILE *fp_wkdir = fopen(".WKDIR", "r");
    fscanf(fp_wkdir, "%s", config_dir);
/*    printf("(debug) %s\n", config_dir);*/
    fclose(fp_wkdir);

    SchmidtCAM_Config MCAM_Config;
    init_SchmidtCAM_Config( &MCAM_Config, config_dir );

    // update vtrans_num & vtrans_skip_num
    MCAM_Config.config_ccd_drive->vtrans_num        = iniparser_getint(scan_param_ini, "SCAN_PARAM:vtrans_num", 500);
    MCAM_Config.config_ccd_drive->vtrans_skip_num   = iniparser_getint(scan_param_ini, "SCAN_PARAM:vtrans_skip_num", 0);
    MCAM_Config.n_row                               = MCAM_Config.config_ccd_drive->vtrans_num;

    printf("**********************************************\n");
    printf("> running in scan mode: \n");
    printf("> rows to be skiped: %d\n", MCAM_Config.config_ccd_drive->vtrans_skip_num);
    printf("> rows to be saved: %d\n", MCAM_Config.n_row);
    printf("**********************************************\n");


    // backup vtrans parameters
    vtrans_num_bak          = MCAM_Config.config_ccd_drive->vtrans_num;
    vtrans_skip_num_bak     = MCAM_Config.config_ccd_drive->vtrans_skip_num;

    printf("vtrans_skip_num : %d\n", vtrans_skip_num_bak);
    // exit(0);

    imgCLK1_cnt_min_bak     = MCAM_Config.config_ccd_drive->imgCLK1_cnt_min;
    imgCLK1_cnt_max_bak     = MCAM_Config.config_ccd_drive->imgCLK1_cnt_max;
    imgCLK2_cnt_min_bak     = MCAM_Config.config_ccd_drive->imgCLK2_cnt_min;
    imgCLK2_cnt_max_bak     = MCAM_Config.config_ccd_drive->imgCLK2_cnt_max;
    imgCLK3_cnt_min_bak     = MCAM_Config.config_ccd_drive->imgCLK3_cnt_min;
    imgCLK3_cnt_max_bak     = MCAM_Config.config_ccd_drive->imgCLK3_cnt_max;
    imgCLK4_cnt_min_bak     = MCAM_Config.config_ccd_drive->imgCLK4_cnt_min;
    imgCLK4_cnt_max_bak     = MCAM_Config.config_ccd_drive->imgCLK4_cnt_max;
    imgCLKx_cnt_MAX_bak     = MCAM_Config.config_ccd_drive->imgCLKx_cnt_MAX;
    vtrans_state_cnt_max_bak= MCAM_Config.config_ccd_drive->vtrans_state_cnt_max;
    
    // backup htrans parameters
    htrans_num_bak          = MCAM_Config.config_ccd_drive->htrans_num;

    //=================================================
    // GPIO 
    // uint32_t *gpio_flt_ctrl;        // ADDR_Float_Point_CTRL
    // uint32_t *gpio_flt_data;        // ADDR_Float_Point_DATA
    // uint32_t *gpio_in;              // ADDR_GPIO_IN
    // uint32_t *gpio_pl_ps;           // ADDR_PL_PS_DDR_CNT
    // uint32_t *gpio_out;             // ADDR_GPIO_OUT
    // uint32_t *gpio_out2;            // ADDR_GPIO_OUT
    // uint32_t *gpio_spi_ps_ctrl;     // ADDR_GPIO_SPI_PS_CTRL
    // uint32_t *gpio_spi_ps_data;     // ADDR_GPIO_SPI_PS_DATA
    //=================================================

	int fd_gpio = open("/dev/mem", O_RDWR | O_SYNC);

    uint32_t *gpio_flt_ctrl     = mmap(NULL, 10, PROT_READ | PROT_WRITE, MAP_SHARED, fd_gpio, ADDR_Float_Point_CTRL);
    uint32_t *gpio_flt_data     = gpio_flt_ctrl + 2;

    uint32_t *gpio_in           = mmap(NULL, 10, PROT_READ | PROT_WRITE, MAP_SHARED, fd_gpio, ADDR_GPIO_IN);
    uint32_t *gpio_pl_ps        = gpio_in + 2;

    uint32_t *gpio_out          = mmap(NULL, 10, PROT_READ | PROT_WRITE, MAP_SHARED, fd_gpio, ADDR_GPIO_OUT);
    uint32_t *gpio_out2         = gpio_out + 2;

    uint32_t *gpio_spi_ps_ctrl  = mmap(NULL, 10, PROT_READ | PROT_WRITE, MAP_SHARED, fd_gpio, ADDR_GPIO_SPI_PS_CTRL);
    uint32_t *gpio_spi_ps_data  = gpio_spi_ps_ctrl + 2;
    
    MCAM_Config.gpio_flt_ctrl   = gpio_flt_ctrl;
    MCAM_Config.gpio_flt_data   = gpio_flt_data;
    MCAM_Config.gpio_in         = gpio_in;
    MCAM_Config.gpio_pl_ps      = gpio_pl_ps;
    MCAM_Config.gpio_out        = gpio_out;
    MCAM_Config.gpio_out2       = gpio_out2;
    MCAM_Config.gpio_spi_ps_ctrl= gpio_spi_ps_ctrl;
    MCAM_Config.gpio_spi_ps_data= gpio_spi_ps_data;
    
    mcfg = &MCAM_Config;

    // int tag_is_ready = 0;
    char tag[16];

    int fout_name_is_ready = 0;
    char fout_name[128];        // 保存临时输出的bin文件名

    //=================================================
    //      SOCKET
    //=================================================
    
    int listenfd = socket(AF_INET,SOCK_STREAM, 0);      // 定义sockfd
    int on = 1;                                         // Enable address reuse
//    int ret = setsockopt( listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on) );
    setsockopt( listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on) );
    
    struct sockaddr_in server_sockaddr;                 ///定义sockaddr_in
    
/*    bzero(&server_sockaddr, sizeof(server_sockaddr));*/
    memset( &server_sockaddr, 0, sizeof(server_sockaddr));
    
    server_sockaddr.sin_family      = AF_INET;
    server_sockaddr.sin_port        = htons(PORT);
    server_sockaddr.sin_addr.s_addr = htonl(INADDR_ANY);

    ///bind成功返回0出错返回-1
    if( bind(listenfd, (struct sockaddr *)&server_sockaddr, sizeof(server_sockaddr)) == -1 )
    {
        fprintf(stderr, "bind function failed.\n");
        exit(-1);
    }

    ///listen成功返回0出错返回-1
    if( listen(listenfd,LISTEN_QUEUE) == -1 )
    {
        fprintf(stderr, "listen function failed.\n");
        exit(-1);
    }

    fprintf(stdout, "listening on %d\n", PORT);

    ///客户端套接字
    char recvbuf[BUFFER_SIZE];
    char sendbuf[BUFFER_SIZE];
    struct sockaddr_in client_addr;
    socklen_t length = sizeof(client_addr);
    bzero(&client_addr, sizeof(client_addr));
    ///成功返回非负描述字出错返回-1
    
    int connsockfd = accept(listenfd, (struct sockaddr*)&client_addr, &length);
    if( connsockfd < 0 )
    {
        fprintf(stderr, "connect function failed.\n");
        exit(-1);
    }
    
    while(1)
    {
        bzero(recvbuf, sizeof(recvbuf));
        bzero(sendbuf, sizeof(sendbuf));

        socklen_t len = recvfrom(connsockfd, recvbuf, sizeof(recvbuf), 0, (struct sockaddr *)&client_addr, &len);

        if( len > 0 ){

/*           printf("\n#### [CSST-CCD-TEST Socket server] recieved: %s, socklen = %d\n\n", recvbuf, len);*/

            if( strcmp(recvbuf, "readout") == 0 ){
                if( 1 == fout_name_is_ready ){

                    // set vtrans parameters to temporary values
                    mcfg->config_ccd_drive->vtrans_num           = vtrans_skip_num_bak;
                    mcfg->config_ccd_drive->vtrans_skip_num      = 0;
                    mcfg->config_ccd_drive->imgCLK1_cnt_min      = 900;
                    mcfg->config_ccd_drive->imgCLK1_cnt_max      = 2100;
                    mcfg->config_ccd_drive->imgCLK2_cnt_min      = 300;
                    mcfg->config_ccd_drive->imgCLK2_cnt_max      = 1500;
                    mcfg->config_ccd_drive->imgCLK3_cnt_min      = 900;
                    mcfg->config_ccd_drive->imgCLK3_cnt_max      = 2100;
                    mcfg->config_ccd_drive->imgCLK4_cnt_min      = 300;
                    mcfg->config_ccd_drive->imgCLK4_cnt_max      = 1500;
                    mcfg->config_ccd_drive->imgCLKx_cnt_MAX      = 4000;
                    mcfg->config_ccd_drive->vtrans_state_cnt_max = 80;
                    
                    // set htrans parameters to temporary values
                    mcfg->config_ccd_drive->htrans_num = 1;

                    // set htrans_num to 1
                    socket_config_ccd_drive(mcfg, 
                                            mcfg->gpio_flt_ctrl, 
                                            mcfg->gpio_flt_data,
                                            mcfg->gpio_in,
                                            mcfg->gpio_pl_ps,
                                            mcfg->gpio_out,
                                            mcfg->gpio_out2,
                                            mcfg->gpio_spi_ps_ctrl,
                                            mcfg->gpio_spi_ps_data );

                    ccd_refresh( mcfg->gpio_out, 
                                mcfg->gpio_in, 
                                mcfg->gpio_pl_ps, 
                                4700,
                                1250,
                                mcfg->n_bytes,
                                0,
                                "" );

                    // reset vtrans parameters
                    mcfg->config_ccd_drive->vtrans_num      = vtrans_num_bak;
                    mcfg->config_ccd_drive->vtrans_skip_num = 0;
                    mcfg->config_ccd_drive->imgCLK1_cnt_min = imgCLK1_cnt_min_bak;
                    mcfg->config_ccd_drive->imgCLK1_cnt_max = imgCLK1_cnt_max_bak;
                    mcfg->config_ccd_drive->imgCLK2_cnt_min = imgCLK2_cnt_min_bak;
                    mcfg->config_ccd_drive->imgCLK2_cnt_max = imgCLK2_cnt_max_bak;
                    mcfg->config_ccd_drive->imgCLK3_cnt_min = imgCLK3_cnt_min_bak;
                    mcfg->config_ccd_drive->imgCLK3_cnt_max = imgCLK3_cnt_max_bak;
                    mcfg->config_ccd_drive->imgCLK4_cnt_min = imgCLK4_cnt_min_bak;
                    mcfg->config_ccd_drive->imgCLK4_cnt_max = imgCLK4_cnt_max_bak;
                    mcfg->config_ccd_drive->imgCLKx_cnt_MAX = imgCLKx_cnt_MAX_bak;
                    mcfg->config_ccd_drive->vtrans_state_cnt_max = vtrans_state_cnt_max_bak;

                    // reset htrans_num
                    mcfg->config_ccd_drive->htrans_num = htrans_num_bak;

                    socket_config_ccd_drive(mcfg, 
                                            mcfg->gpio_flt_ctrl, 
                                            mcfg->gpio_flt_data,
                                            mcfg->gpio_in,
                                            mcfg->gpio_pl_ps,
                                            mcfg->gpio_out,
                                            mcfg->gpio_out2,
                                            mcfg->gpio_spi_ps_ctrl,
                                            mcfg->gpio_spi_ps_data );
                    gettimeofday(&tv_read_start, NULL);
                    
                    ccd_readout( gpio_out, 
                                 gpio_in, 
                                 gpio_pl_ps, 
                                 MCAM_Config.n_row,
                                 MCAM_Config.n_col,
                                 MCAM_Config.n_bytes,
                                 0,
                                 fout_name );

                    gettimeofday(&tv_read_end, NULL);
                    printf("> [CSST-CCD-TEST Socket server] readout done, used %10.6f seconds\n", 
                            time_diff(&tv_read_start, &tv_read_end));
                            
                    strcpy(sendbuf, "readoutdone");
                    send(connsockfd, sendbuf, strlen(sendbuf), 0);
                    fout_name_is_ready = 0;
                    
//                    bzero(recvbuf, sizeof(recvbuf));
//                    bzero(sendbuf, sizeof(sendbuf));
                } else {
                    printf("> fout_name is not ready, readout will no be executed ...\n");
                }
            }
            else if( strcmp(recvbuf, "refresh") == 0 ){ // 执行刷新

                if( USE_PINNING == 1 ){
                    printf("> [CSST-CCD-TEST Socket server] pinning start ...\n");
                    
                    gettimeofday(&tv_pin_start, NULL);
                    
                    socket_pinning( mcfg->gpio_out, 1 );
                    usleep(150);    // 暂时设置为固定值150us,后续更新为从驱动参数结构体中读取
                    socket_pinning( mcfg->gpio_out, 0 );
                    
                    gettimeofday(&tv_pin_end, NULL);
                    
                    printf("> [CSST-CCD-TEST Socket server] pinning used %10.6f seconds\n", 
                            time_diff(&tv_pin_start,&tv_pin_end));
                }
                
                gettimeofday(&tv_pin_start, NULL);
                
                // set vtrans parameters to temporary values
                mcfg->config_ccd_drive->vtrans_num           = 4700;
                mcfg->config_ccd_drive->vtrans_skip_num      = 0;
                mcfg->config_ccd_drive->imgCLK1_cnt_min      = 900;
                mcfg->config_ccd_drive->imgCLK1_cnt_max      = 2100;
                mcfg->config_ccd_drive->imgCLK2_cnt_min      = 300;
                mcfg->config_ccd_drive->imgCLK2_cnt_max      = 1500;
                mcfg->config_ccd_drive->imgCLK3_cnt_min      = 900;
                mcfg->config_ccd_drive->imgCLK3_cnt_max      = 2100;
                mcfg->config_ccd_drive->imgCLK4_cnt_min      = 300;
                mcfg->config_ccd_drive->imgCLK4_cnt_max      = 1500;
                mcfg->config_ccd_drive->imgCLKx_cnt_MAX      = 4000;
                mcfg->config_ccd_drive->vtrans_state_cnt_max = 80;
                
                // set htrans parameters to temporary values
                mcfg->config_ccd_drive->htrans_num = 1;

                // set htrans_num to 1
                socket_config_ccd_drive(mcfg, 
                                        mcfg->gpio_flt_ctrl, 
                                        mcfg->gpio_flt_data,
                                        mcfg->gpio_in,
                                        mcfg->gpio_pl_ps,
                                        mcfg->gpio_out,
                                        mcfg->gpio_out2,
                                        mcfg->gpio_spi_ps_ctrl,
                                        mcfg->gpio_spi_ps_data );

                gettimeofday(&tv_pin_end, NULL);
                printf("> [CCD_DRIVE] set to fast refresh mode.\n");
                printf("> updating drv params used: %10.6f seconds\n", 
                            time_diff(&tv_pin_start,&tv_pin_end));

                gettimeofday(&tv_pin_start, NULL);
                // fast refresh
                ccd_refresh( mcfg->gpio_out, 
                             mcfg->gpio_in, 
                             mcfg->gpio_pl_ps, 
                             mcfg->n_row,
                             mcfg->n_col,
                             mcfg->n_bytes,
                             0,
                             "" );
                gettimeofday(&tv_pin_end, NULL);
                printf("> refreshing used: %10.6f seconds\n", 
                            time_diff(&tv_pin_start,&tv_pin_end));

                gettimeofday(&tv_pin_start, NULL);
                // reset vtrans parameters
                mcfg->config_ccd_drive->vtrans_num      = vtrans_num_bak;
                mcfg->config_ccd_drive->vtrans_skip_num = vtrans_skip_num_bak;
                mcfg->config_ccd_drive->imgCLK1_cnt_min = imgCLK1_cnt_min_bak;
                mcfg->config_ccd_drive->imgCLK1_cnt_max = imgCLK1_cnt_max_bak;
                mcfg->config_ccd_drive->imgCLK2_cnt_min = imgCLK2_cnt_min_bak;
                mcfg->config_ccd_drive->imgCLK2_cnt_max = imgCLK2_cnt_max_bak;
                mcfg->config_ccd_drive->imgCLK3_cnt_min = imgCLK3_cnt_min_bak;
                mcfg->config_ccd_drive->imgCLK3_cnt_max = imgCLK3_cnt_max_bak;
                mcfg->config_ccd_drive->imgCLK4_cnt_min = imgCLK4_cnt_min_bak;
                mcfg->config_ccd_drive->imgCLK4_cnt_max = imgCLK4_cnt_max_bak;
                mcfg->config_ccd_drive->imgCLKx_cnt_MAX = imgCLKx_cnt_MAX_bak;
                mcfg->config_ccd_drive->vtrans_state_cnt_max = vtrans_state_cnt_max_bak;

                // reset htrans_num
                mcfg->config_ccd_drive->htrans_num = htrans_num_bak;

                socket_config_ccd_drive(mcfg, 
                                        mcfg->gpio_flt_ctrl, 
                                        mcfg->gpio_flt_data,
                                        mcfg->gpio_in,
                                        mcfg->gpio_pl_ps,
                                        mcfg->gpio_out,
                                        mcfg->gpio_out2,
                                        mcfg->gpio_spi_ps_ctrl,
                                        mcfg->gpio_spi_ps_data );

                gettimeofday(&tv_pin_end, NULL);
                printf("> [CCD_DRIVE] reset to nomal readout mode.\n");
                printf("> reset drv params time used is %10.6f seconds\n", 
                            time_diff(&tv_pin_start,&tv_pin_end));

                strcpy(sendbuf, "refreshdone");
                send(connsockfd, sendbuf, strlen(sendbuf), 0);
/*                printf("#### replied refreshdone\n");*/
                bzero(recvbuf, sizeof(recvbuf));
                bzero(sendbuf, sizeof(sendbuf));
            }
            else if( strcmp(recvbuf, "expstart") == 0 ){
                gettimeofday(&tv_exp_start, NULL);
                printf("> [CSST-CCD-TEST Socket server] exp start ...\n");
            }
            else if( strcmp(recvbuf, "expdone") == 0 ){
                gettimeofday(&tv_exp_end, NULL);
                printf("> [CSST-CCD-TEST Socket server] exp done ...\n");
                printf("> [CSST-CCD-TEST Socket server] exp time %10.6f seconds.\n", 
                        time_diff(&tv_exp_start,&tv_exp_end) );
            }
            else if( strlen(recvbuf) == 9 ){

/*                printf("> [CSST-CCD-TEST Socket server] received tag: %s\n", recvbuf);*/
                sprintf(tag, "%s", recvbuf);
                sprintf(fout_name, "tmp/%s.bin", recvbuf);
/*                printf("####### %s\n", fout_name);*/
                fout_name_is_ready = 1;

                usleep(5000);   // 5ms
                strcpy(sendbuf, "tagreceived");
                send(connsockfd, sendbuf, strlen(sendbuf), 0);
/*                printf("> sent %s\n", sendbuf);*/
                bzero(recvbuf, sizeof(recvbuf));
                bzero(sendbuf, sizeof(sendbuf));
            }
            else if( strcmp(recvbuf, "exit") == 0 ){

                strcpy(sendbuf, "exit_ok");
                send(connsockfd, sendbuf, strlen(sendbuf), 0);
                break;
            }        
        }

//        sleep(1);        
        usleep(1000);
    }

    close(connsockfd);
    close(listenfd);

    free_SchmidtCAM_Config( &MCAM_Config );

    iniparser_freedict(scan_param_ini);

    munmap(gpio_flt_ctrl, 10);
    munmap(gpio_in, 10);
    munmap(gpio_out, 10);
    munmap(gpio_spi_ps_ctrl, 10);

	return 0;
}
